<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>在线视频聊天室</title>

    <link rel="stylesheet" href="css/@tdesign-vue-next/chat.css">
    <script src="js/vue.global.js"></script>
    <script src="js/@tdesign-vue-next/chat.js"></script>
    <script src="./js/sober.min.js"></script>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
        }
        body {
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
        }

        textarea {
            overflow-x: hidden;
            overflow-y: auto;
        }

        table,
        tr,
        th,
        td {
            border-collapse: collapse;
            border: 1px solid rgba(128, 128, 128, 0.5);
            box-sizing: border-box;
            padding: 0.5em;
        }

        video {
            background-color: black;
            max-height: 100%;
        }

        .panel {
            display: flex;
            flex-direction: column;
            gap: 0.5em;

            box-sizing: border-box;
            margin: 1em;
            padding: 1em;
            box-shadow: 2px 2px 5px 2px rgba(128, 128, 128, 0.2);
        }
        .border {
            border: 1px solid rgba(128, 128, 128, 0.5);
            box-sizing: border-box;
            padding: 1em;
        }

        .flex-1 {
            flex: 1;
        }
        .flex-column {
            display: flex;
            flex-direction: column;
        }
        .flex-row {
            display: flex;
            flex-direction: row;
        }
        .flex-wrap {
            flex-wrap: wrap;
        }
        .justify-between {
            justify-content: space-between;
        }
        .gap-05em {
            gap: 0.5em;
        }

        s-page {
            width: 100%;
            height: 100%;
        }

        s-scroll-view > * {
            margin-top: 0.25em;
        }

        .sidebar {
            display: flex;
            flex-direction: column;
            width: 100%;
            height: 100%;
        }

        textarea {
            resize: none;
        }

        #container-main {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        #container-chat-public,
        #container-chat,
        #container-video {
            flex: 1;
            box-sizing: border-box;
            padding: 0.25em;
            display: flex;
            flex-direction: column;
            padding-bottom: 1em;
        }

        #container-chat-public > *,
        #container-chat > *,
        #container-video > * {
            margin-top: 0.5em;
        }


        /* @tdesign-vue-next/chat */
        #container-chat-public,
        #container-chat {
            overflow: hidden;
            box-sizing: border-box;
            padding: 1em;
        }
        #container-chat-public button,
        #container-chat button {
            height: 48px;
        }

        #container-chat-app {
            overflow: hidden;
            flex: 1;
            display: flex;
            flex-direction: column;
        }

        .t-chat {
            overflow: hidden;
        }
        .t-chat__base {
            padding-left: 0;
        }
        .t-chat__text {
            padding-top: 0;
        }

    </style>
</head>
<body>
    <s-page>
        <s-drawer>
            <div slot="start">
                <s-scroll-view class="sidebar">
                    <s-ripple style="padding: 1em 2em">公共聊天区</s-ripple>
                    <s-ripple style="padding: 1em 2em">私聊</s-ripple>
                    <s-ripple style="padding: 1em 2em">视频通话</s-ripple>
                </s-scroll-view>
            </div>
            <div class="flex-column">
                <s-appbar>
                    <!--左侧菜单按钮-->
                    <s-icon-button slot="navigation" onclick="document.querySelector('s-drawer').toggle()">
                        <s-icon type="menu"></s-icon>
                    </s-icon-button>
                    <!--标题-->
                    <div slot="headline">在线视频聊天室（公共聊天区）</div>
                </s-appbar>
                <main id="container-main">
                    <!-- 公共聊天区 -->
                    <!--                     
                    <div id="container-chat-public">
                        <textarea
                            id="textarea-message-public"
                            style="width: 99%;height: 100%;min-height: 10rem;"
                            readonly
                        ></textarea>
                        <div>
                            <form id="form-message-public" style="display: flex;width: 100%;gap: 0.25em;">
                                <s-text-field label="请输入消息内容" style="flex: 1;">
                                    <input
                                        id="input-message-public"
                                        name="message"
                                        type="text"
                                        required
                                    />
                                </s-text-field>
                                <input id="button-send-message-public" type="submit" value="发送"></input>
                            </form>
                        </div>
                    </div>
                    -->

                    <!-- 公共聊天区——@tdesign-vue-next/chat -->
                    <div id="container-chat-public">
                        <t-chat
                            :reverse="false"
                            :clear-history="false"
                            :data="messageList"
                        ></t-chat>
                        <div>
                            <t-chat-input
                                v-model="message"
                                :textarea-props="{
                                    placeholder: '请输入消息...',
                                }"
                                @change="onInputMessage"
                                @send="onSendMessage"
                            ></t-chat-input>
                        </div>
                    </div>

                    <!-- 私聊 -->
                    <div id="container-chat" style="display: none;">
                        <div class="flex-row flex-wrap" style="gap: 0.5em;">
                            <s-chip class="address-local">
                                本地地址：
                            </s-chip>
                            <s-chip class="state-connection-chat">
                                连接状态：未连接
                            </s-chip>
                        </div>
                        <div>
                            <s-bottom-sheet>
                                <s-button
                                    id="button-open-connect-chat"
                                    slot="trigger"
                                    type="outlined"
                                    style="height: 2.5em;"
                                >
                                    连接
                                </s-button>
                                <div style="padding: 0.5em;">
                                    <s-table id="table-user-chat">
                                        <s-thead>
                                            <s-tr>
                                                <s-th colspan="3">在线用户</s-th>
                                            </s-tr>
                                            <s-tr>
                                                <s-th>昵称</s-th>
                                                <s-th>状态</s-th>
                                                <s-th>操作</s-th>
                                            </s-tr>
                                        </s-thead>
                                        <s-tbody id="table-body-user-chat">
                                            <template id="template-chat-tr-user">
                                                <s-tr>
                                                    <s-td class="td-address"></s-td>
                                                    <s-td class="td-state"></s-td>
                                                    <s-td>
                                                        <s-button class="button-connect" type="outlined">连接</s-button>
                                                    </s-td>
                                                </s-tr>
                                            </template>
                                        </s-tbody>
                                    </s-table>
                                </div>
                            </s-bottom-sheet>
                        </div>

                        <!--  -->
                        <!--
                        <textarea
                            id="textarea-message"
                            style="width: 99%;height: 100%;min-height: 10rem;"
                            readonly
                        ></textarea>
                        <form id="form-message" style="display: flex;width: 100%;gap: 0.25em;">
                            <s-text-field label="请输入消息内容" style="flex: 1;">
                                <input
                                    name="message"
                                    type="text"
                                    required
                                >
                            </s-text-field>
                            <input id="button-send" type="submit" value="发送" disabled/>
                        </form>
                        -->

                        <!-- @tdesign-vue-next/chat -->
                        <div id="container-chat-app">
                            <t-chat
                                :reverse="false"
                                :clear-history="false"
                                :data="messageList"
                            ></t-chat>
                            <div>
                                <t-chat-input
                                    :disabled="disabled"
                                    v-model="message"
                                    :textarea-props="{
                                        placeholder: '请输入消息...',
                                    }"
                                    @change="onInputMessage"
                                    @send="onSendMessage"
                                ></t-chat-input>
                            </div>
                        </div>
                        
                    </div>

                    <!-- 视频通话 -->
                    <div id="container-video" style="display: none;">
                        <div class="flex-row flex-wrap" style="gap: 0.5em;">
                            <s-chip class="address-local">
                                本地地址：
                            </s-chip>
                            <s-chip class="state-connection-video">
                                连接状态：未连接
                            </s-chip>
                        </div>

                        <div class="flex-row flex-wrap" style="gap: 0.5em;">
                            <s-bottom-sheet>
                                <s-button
                                    id="button-open-connect-video"
                                    slot="trigger"
                                    type="outlined"
                                >
                                    连接
                                </s-button>
                                <div style="padding: 0.5em;">
                                    <s-table id="table-user-video">
                                        <s-thead>
                                            <s-tr>
                                                <s-th colspan="3">在线用户</s-th>
                                            </s-tr>
                                            <s-tr>
                                                <s-th>昵称</s-th>
                                                <s-th>状态</s-th>
                                                <s-th>操作</s-th>
                                            </s-tr>
                                        </s-thead>
                                        <s-tbody id="table-body-user-video">
                                            <template id="template-video-tr-user">
                                                <s-tr>
                                                    <s-td class="td-address"></s-td>
                                                    <s-td class="td-state"></s-td>
                                                    <s-td>
                                                        <s-button class="button-connect" type="outlined">连接</s-button>
                                                    </s-td>
                                                </s-tr>
                                            </template>
                                        </s-tbody>
                                    </s-table>
                                </div>
                            </s-bottom-sheet>
                            <s-button id="button-close-video" type="outlined" disabled="true">关闭连接</s-button>
                        </div>

                        <div
                            class="flex-row flex-1"
                            style="gap: 0.5em;align-items: flex-start;box-sizing: border-box;padding: 0.5em;max-height: 50%;"
                        >
                            <video id="video-remote" style="width: 75%;" autoplay></video>
                            <video id="video-local" style="width: 25%;" autoplay></video>
                        </div>
                    </div>
                </main>
            </div>
        </s-drawer>
    </s-page>

    <script>
        // 侧边栏
        const VIEW = {
            PUBLIC_CHAT: "container-chat-public",
            PRIVATE_CHAT: "container-chat",
            VIDEO: "container-video",
        };
        var currentView = VIEW.PUBLIC_CHAT;
        const title = "在线视频聊天室";
        const titleBar = document.querySelector("s-appbar").querySelector("[slot=headline]");
        const drawer = document.querySelector("s-drawer");
        const sidebar = document.querySelector(".sidebar");
        const mainContainer = document.querySelector("#container-main");
        sidebar.onclick = () => {
            for (const child of mainContainer.children) {
                if (child.id !== currentView) {
                    child.style.display = "none";
                } else {
                    child.style.display = "";
                }
            }
        }
        sidebar.children[0].onclick = () => {
            currentView = VIEW.PUBLIC_CHAT;
            titleBar.textContent = `${title}（公共聊天区）`;
            drawer.toggle();
        }
        sidebar.children[1].onclick = () => {
            currentView = VIEW.PRIVATE_CHAT;
            titleBar.textContent = `${title}（私聊）`;
            drawer.toggle();
        }
        sidebar.children[2].onclick = () => {
            currentView = VIEW.VIDEO;
            titleBar.textContent = `${title}（视频通话）`;
            drawer.toggle();
        }


        class UserData {
            nickname = ""; // 用户昵称
            address = ""; // 用户地址

            constructor(nickname, address) {
                this.nickname = nickname;
                this.address = address;
            }

            getID() {
                return `${this.nickname}@${this.address}`;
            }
        }
        class Command {
            static TYPE_LOGIN = "login";
            static TYPE_TEXT_PUBLIC = "text_public";
            static TYPE_TEXT = "text";
            static TYPE_OFFER = "offer";
            static TYPE_ANSWER = "answer";
            static TYPE_CANDIDATE = "candidate";

            constructor(type, nickname, to, data) {
                this.type = type;
                this.nickname = nickname;
                if (type !== Command.TYPE_TEXT_PUBLIC) {
                    this.to = to;
                }
                this.data = data;
            }
        }
        class Message {
            static TYPE_LOGIN_SUCCESS = "login_success";
            static TYPE_ADDRESS = "address";
            static TYPE_TEXT_PUBLIC = "text_public";
            static TYPE_TEXT = "text";
            static TYPE_OFFER = "offer";
            static TYPE_ANSWER = "answer";
            static TYPE_CANDIDATE = "candidate";
            constructor(text) {
                try {
                    const m = JSON.parse(text);
                    this.type = m.type;
                    this.nickname = m.nickname;
                    this.from = m.from;
                    this.time = m.time;
                    this.data = m.data;
                } catch (e) {
                    console.error(e);
                }
            }
        }
        class ChatMessage {
            constructor(role, name, datetime, content) {
                if (name === "momo") {
                    this.avatar = "image/avatar-momo.webp";
                } else {
                    this.avatar = "https://api.zxki.cn/api/sjtx?type=image&name=" + encodeURIComponent(name);
                }
                this.role = role;
                this.name = name;
                this.datetime = datetime;
                this.content = content;
            }

            /**
             * @param { "assistant" | "user" } role
             * @param { Message } message
            */
            static fromMessage(role, message) {
                return new ChatMessage(role, message.nickname || message.from, new Date(message.time).toLocaleString(), message.data);
            }
        }

        const wsClient = (function() {
            const localAddress = "localhost:5555";
            // const serverAddress = "liveroom.cyue.net";
            if (window.location.href.includes("https")) {
                return new WebSocket(`wss://${localAddress}`, "cyue-webrtc");
            }
            return new WebSocket(`ws://${localAddress}`, "cyue-webrtc")
        }());
        const pingTimer = setInterval(() => {
            wsClient.send("ping");
        }, 30000)
        wsClient.onclose = () => {
            clearInterval(pingTimer);
            console.log("connection closed");
            // window.close();
        };

        const SIGNAL_SERVICE = {
            nickname: (function() {
                const nickname = window.prompt("请输入昵称") || "momo";
                const loginCMD = new Command(Command.TYPE_LOGIN, nickname, undefined, nickname);
                wsClient.onopen = () => {
                    wsClient.send(JSON.stringify(loginCMD));
                }
                return nickname;
            }()),
            localAddress: undefined,
            chatTo: undefined,
            videoTo: undefined,

            /**
             * 获取用户ID
             * @param { { nickname: string, address: string } } user
             */
            getUserID(user) {
                return new UserData(user.nickname, user.address).getID();
            },
            getLocalUser() {
                const u = {
                    nickname: this.nickname,
                    address: this.localAddress,
                }
                return {
                    id: this.getUserID(u),
                    ...u
                }
            },

            /**
             * 设置聊天对象
             * @param { string } to - 用户ID
             */
            setChatTo(to) {
                this.chatTo = to;
            },

            /**
             * 设置视频对象
             * @param { string } to - 用户ID
             */
            setVideoTo(to) {
                this.videoTo = to;
            },
            sendPublicText(text) {
                const cmd = new Command(Command.TYPE_TEXT_PUBLIC, this.nickname, undefined, text);
                wsClient.send(JSON.stringify(cmd));
            },
            sendText(text) {
                if (!this.chatTo) {
                    console.warn("没有发送对象");
                    return;
                }
                const cmd = new Command(Command.TYPE_TEXT, this.nickname, this.chatTo, text);
                wsClient.send(JSON.stringify(cmd));
            },
            sendOffer(offer) {
                if (!this.videoTo) {
                    return;
                }
                const cmd = new Command(Command.TYPE_OFFER, this.nickname, this.videoTo, offer);
                wsClient.send(JSON.stringify(cmd));
            },
            sendAnswer(answer) {
                if (!this.videoTo) {
                    return;
                }
                const cmd = new Command(Command.TYPE_ANSWER, this.nickname, this.videoTo, answer);
                wsClient.send(JSON.stringify(cmd));
            },
            sendCandidate(candidate) {
                if (!this.videoTo) {
                    return;
                }
                const cmd = new Command(Command.TYPE_CANDIDATE, this.nickname, this.videoTo, candidate);
                wsClient.send(JSON.stringify(cmd));
            }
        };

        // 公共聊天区域初始化
        const publicChatContainer = Vue.createApp({
            data() {
                return {
                    message: "",
                    messageList: []
                };
            },
            methods: {
                onInputMessage(message) {
                    console.log("输入公共消息：", message);
                },
                onSendMessage(message) {
                    console.log("发送公共消息：", message);
                    SIGNAL_SERVICE.sendPublicText(message);
                    this.message = "";
                }
            },
        })
        .use(TDesign.default)
        .mount("#container-chat-public");


        // 私聊区域初始化
        const chatContainer = Vue.createApp({
            data() {
                return {
                    disabled: true,
                    message: "",
                    messageList: []
                };
            },
            methods: {
                onInputMessage(message) {
                    console.log("输入消息：", message);
                },
                onSendMessage(message) {
                    console.log("发送消息：", message);
                    SIGNAL_SERVICE.sendText(message);
                    this.message = "";
                }
            },
        })
        .use(TDesign.default)
        .mount("#container-chat-app");


        // 私聊页与视频页初始化
        const CONNECTION_STATE = {
            DISCONNECTED: "已断开连接",
            TIMEOUT: "连接超时",
            AWAITING: "等待对方接受",
            FAILED: "连接失败",
            CONNECTING: "连接中",
            CONNECTED: "已连接",
        };
        let connectionState = CONNECTION_STATE.DISCONNECTED;
        let connected = false;
        let localMediaStream; // 本地媒体流
        let remoteMediaStream = new MediaStream(); // 远程媒体流
        const chatConnectStateElement = document.querySelector(".state-connection-chat");
        const videoConnectStateElement = document.querySelector(".state-connection-video");
        const closeButton = document.getElementById("button-close-video");
        // const sendMessageButton = document.getElementById("button-send");
        // const messageElement = document.getElementById("textarea-message");
        // const publicMessageElement = document.getElementById("textarea-message-public");
        const videoLocal = document.getElementById("video-local");
        const videoRemote = document.getElementById("video-remote");

        function updateVideoConnectionState(nState) {
            connectionState = nState;
            videoConnectStateElement.textContent = `连接状态：${connectionState}`;
        }

        // 修改按钮状态
        setInterval(() => {
            if (SIGNAL_SERVICE.chatTo) {
                chatConnectStateElement.textContent = "连接状态：已连接";
                // sendMessageButton.disabled = false;
                chatContainer.disabled = false;
            } else {
                chatConnectStateElement.textContent = "连接状态：未连接";
                // sendMessageButton.disabled = true;
                chatContainer.disabled = true;
            }
            if (connectionState === CONNECTION_STATE.CONNECTED) {
                closeButton.disabled = false;
            } else {
                closeButton.disabled = true;
            }
        }, 5000);

        function closeConnection() {
            connection.close();
            connection = null;
            updateVideoConnectionState(CONNECTION_STATE.DISCONNECTED);
            closeButton.disabled = true;
            // sendMessageButton.disabled = true;
            chatContainer.disabled = true;
            videoRemote.srcObject = null;
            videoRemote.src = null;
            remoteMediaStream = new MediaStream();
        }
        closeButton.onclick = closeConnection;

        function createConnection(tracks) {
            const peer = new RTCPeerConnection({
                iceServers: [
                    {
                        urls: "stun:stunserver2024.stunprotocol.org"
                    }
                ]
            });
            peer.onicecandidate = (e) => {
                if (e.candidate) {
                    SIGNAL_SERVICE.sendCandidate(e.candidate);
                }
            }
            peer.onconnectionstatechange = (e) => {
                console.log(e.target.connectionState);
                if (e.target.connectionState === "connected") {
                    updateVideoConnectionState(CONNECTION_STATE.CONNECTED);
                } else if (e.target.connectionState === "connecting") {
                    updateVideoConnectionState(CONNECTION_STATE.CONNECTING);
                } else if (e.target.connectionState === "disconnected") {
                    updateVideoConnectionState(CONNECTION_STATE.DISCONNECTED);
                    closeConnection();
                } else if (e.target.connectionState === "failed") {
                    updateVideoConnectionState(CONNECTION_STATE.FAILED);
                    closeConnection();
                } else {
                    updateVideoConnectionState(CONNECTION_STATE.DISCONNECTED);
                }
            }
            peer.ontrack = (event) => {
                console.log(event);
                remoteMediaStream.addTrack(event.track);
                if ("srcObject" in videoRemote) {
                    videoRemote.srcObject = remoteMediaStream;
                } else {
                    // 避免在新版浏览器中使用此方法，因为它即将被淘汰。
                    videoRemote.src = URL.createObjectURL(remoteMediaStream);
                }
            };
            for (const track of tracks) {
                peer.addTrack(track);
            }
            return peer;
        }

        async function createOfferConnection(tracks, timeout = 30000) {
            const peer = createConnection(tracks);
            const offer = await peer.createOffer();
            await peer.setLocalDescription(offer);
            SIGNAL_SERVICE.sendOffer(offer);
            updateVideoConnectionState(CONNECTION_STATE.AWAITING);
            setTimeout(() => {
                if (
                    connectionState === CONNECTION_STATE.CONNECTED ||
                    connectionState === CONNECTION_STATE.CONNECTING
                ) {
                    return;
                }
                updateVideoConnectionState(CONNECTION_STATE.TIMEOUT);
                closeConnection();
            }, timeout);
            return peer;
        }

        async function createAnswerConnection(offer, tracks, timeout = 30000) {
            const peer = createConnection(tracks);
            await peer
                .setRemoteDescription(offer)
                .catch((err) => {
                    console.error(err);
                    alert("连接出错");
                });
            const answer = await peer.createAnswer();
            await peer.setLocalDescription(answer);
            SIGNAL_SERVICE.sendAnswer(answer);
            setTimeout(() => {
                if (
                    connectionState === CONNECTION_STATE.CONNECTED ||
                    connectionState === CONNECTION_STATE.CONNECTING
                ) {
                    return;
                }
                updateVideoConnectionState(CONNECTION_STATE.TIMEOUT);
                closeConnection();
            }, timeout);
            return peer;
        }

        let connection; // RTCPeerConnection
        wsClient.onmessage = async (e) => {
            const messageText = e.data;
            if (messageText.toLowerCase() === "pong") {
                console.log(messageText);
                return;
            }

            const message = new Message(messageText);
            const messageTime = new Date(message.time);
            console.log(message);
            switch(message.type) {
                case Message.TYPE_LOGIN_SUCCESS:
                    console.log("登录成功");
                    break;
                case Message.TYPE_ADDRESS: {
                    SIGNAL_SERVICE.localAddress = message.data;
                    const addressElements = document.querySelectorAll(".address-local");
                    for(const c of addressElements) {
                        c.textContent = "本地地址：" + SIGNAL_SERVICE.localAddress;
                    }
                    break;
                }
                case Message.TYPE_TEXT_PUBLIC: {
                    if (message.from === SIGNAL_SERVICE.localAddress) {
                        message.from = "self";
                    }
                    // publicMessageElement.value += `${message.nickname || message.from}（${messageTime.toLocaleString()}）：${message.data}\n`;
                    const role = message.from === "self" ? "user" : "assistant";
                    const cm = ChatMessage.fromMessage(role, message);
                    publicChatContainer.messageList.push(cm);
                    break;
                }
                case Message.TYPE_TEXT: {
                    if (message.from === SIGNAL_SERVICE.getLocalUser().id) {
                        message.from = "self";
                    }
                    // messageElement.value += `${message.nickname || message.from}（${messageTime.toLocaleString()}）：${message.data}\n`;
                    const role = message.from === "self" ? "user" : "assistant";
                    const cm = ChatMessage.fromMessage(role, message);
                    chatContainer.messageList.push(cm);
                    break;
                }
                case Message.TYPE_OFFER: {
                    if (
                        connectionState === CONNECTION_STATE.CONNECTED ||
                        connectionState === CONNECTION_STATE.CONNECTING
                    ) {
                        return;
                    }
                    if (!window.confirm(`“${message.nickname || message.from}”想要与你进行视频通话，是否接受？`)) {
                        return;
                    }
                    updateVideoConnectionState(CONNECTION_STATE.CONNECTING);
                    SIGNAL_SERVICE.setVideoTo(message.from);
                    const offer = message.data;
                    connection = await createAnswerConnection(
                        offer,
                        localMediaStream ? localMediaStream.getTracks() : []
                    );
                    break;
                }
                case Message.TYPE_ANSWER: {
                    const receivedAnswer = message.data;
                    if (connection) {
                        updateVideoConnectionState(CONNECTION_STATE.CONNECTING);
                        await connection
                            .setRemoteDescription(receivedAnswer)
                            .catch((err) => {
                                console.error(err);
                                alert("连接出错");
                            });
                    } else {
                        alert("连接出错");
                    }
                    break;
                }
                case Message.TYPE_CANDIDATE: {
                    const candidate = message.data;
                    if (connection) {
                        await connection.addIceCandidate(candidate);
                    }
                    break;
                }
            }
        };


        // 发送消息
        // const publicMessageForm = document.getElementById("form-message-public");
        // const messageForm = document.getElementById("form-message");
        // publicMessageForm.onsubmit = (e) => {
        //     e.preventDefault();
        //     const messageText = publicMessageForm.message.value;
        //     console.log("发送公共消息：", messageText);
        //     SIGNAL_SERVICE.sendPublicText(messageText);
        //     publicMessageForm.reset();
        // };
        // messageForm.onsubmit = (e) => {
        //     e.preventDefault();
        //     const messageText = messageForm.message.value;
        //     console.log("发送消息：", messageText);
        //     SIGNAL_SERVICE.sendText(messageText);
        //     messageForm.reset();
        // };

        // 更新用户列表
        /** @type { HTMLTableElement } */
        const userChatTableBody = document.getElementById("table-body-user-chat");
        const userVideoTableBody = document.getElementById("table-body-user-video");
        const userChatTrTemplate = document.querySelector("#template-chat-tr-user").content.children[0].cloneNode(true);
        const userVideoTrTemplate = document.querySelector("#template-video-tr-user").content.children[0].cloneNode(true);
        function refreshUserData() {

            const localUserID = SIGNAL_SERVICE.getUserID({
                nickname: SIGNAL_SERVICE.nickname,
                address: SIGNAL_SERVICE.localAddress
            });

            fetch("/user/list")
            .then(res => res.json())
            .then(data => {
                userChatTableBody.innerHTML = "";
                userVideoTableBody.innerHTML = "";
                setTimeout(() => {
                    for (const user of data) {
                        const remoteUserID = SIGNAL_SERVICE.getUserID(user);

                        // 跳过自己
                        if (localUserID === remoteUserID) {
                            continue;
                        }

                        const chatTr = userChatTrTemplate.cloneNode(true);
                        const chatConnectButton = chatTr.querySelector(".button-connect");
                        chatTr.children[0].textContent = user.nickname;
                        chatTr.children[1].textContent = "在线";
                        chatConnectButton.onclick = async () => {
                            // 如果是自己，则不允许连接
                            if (localUserID === remoteUserID) {
                                alert("不能连接自己");
                                return;
                            }
                            console.log("连接", user);
                            SIGNAL_SERVICE.setChatTo(remoteUserID);
                            chatConnectStateElement.textContent = "连接状态：已连接";
                        };
                        userChatTableBody.appendChild(chatTr);

                        const videoTr = userVideoTrTemplate.cloneNode(true);
                        const videoConnectButton = videoTr.querySelector(".button-connect");
                        videoTr.children[0].textContent = user.nickname;
                        videoTr.children[1].textContent = "在线";
                        if (
                            connectionState === CONNECTION_STATE.AWAITING ||
                            connectionState === CONNECTION_STATE.CONNECTING ||
                            connectionState === CONNECTION_STATE.CONNECTED
                        ) {
                            videoConnectButton.disabled = true;
                            videoConnectButton.setAttribute("disabled", "true");
                            userVideoTableBody.appendChild(videoTr);
                            continue;
                        }

                        videoConnectButton.onclick = async () => {
                            // 如果是自己，则不允许连接
                            if (localUserID === remoteUserID) {
                                alert("不能连接自己");
                                return;
                            }

                            console.log("连接", user);
                            userVideoTableBody.querySelectorAll(".button-connect").forEach(item => item.disabled = true);
                            SIGNAL_SERVICE.setVideoTo(remoteUserID);
                            connection = await createOfferConnection(
                                localMediaStream ? localMediaStream.getTracks() : []
                            );
                        };
                        userVideoTableBody.appendChild(videoTr);
                    }
                }, 0)
            });
        }
        document.getElementById("button-open-connect-chat").addEventListener("click", refreshUserData);
        document.getElementById("button-open-connect-video").addEventListener("click", refreshUserData);

        // 获取本地视频流
        (async function() {
            const mediaStreamConstraint = {
                audio: true,
                video: true,
            };

            localMediaStream = await navigator
                .mediaDevices
                .getUserMedia(mediaStreamConstraint)
                .catch((err) => {
                    console.error(err);
                });
            if (!localMediaStream) {
                localMediaStream = await navigator
                    .mediaDevices
                    .getDisplayMedia(mediaStreamConstraint)
                    .catch((err) => {
                        console.error(err);
                    });
            }
            if (!localMediaStream) {
                alert("No media stream");
                return;
            }
            const showMediaStream = new MediaStream();
            localMediaStream
            .getTracks()
            .forEach(item => {
                if (item.kind === "video") {
                    showMediaStream.addTrack(item);
                }
            });
            if ("srcObject" in videoLocal) {
                videoLocal.srcObject = showMediaStream;
            } else {
                // 避免在新版浏览器中使用此方法，因为它即将被淘汰。
                videoLocal.src = URL.createObjectURL(showMediaStream);
            }
        })();



        </script>
</body>
</html>
